﻿using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;

/// <summary>
/// 
/// </summary>
public class SLogger : ILogger
{
    private readonly string _categoryName;
    private readonly SLoggerConfiguration _sLoggerConfiguration;
    private readonly string path = Path.Combine(AppContext.BaseDirectory, "logs");
    private readonly UTF8Encoding uTF8Encoding = new UTF8Encoding(false);
    private object _lock = new object();

    /// <summary>
    /// 
    /// </summary>
    /// <param name="categoryName"></param>
    /// <param name="sLoggerConfiguration"></param>
    public SLogger(string categoryName, SLoggerConfiguration sLoggerConfiguration)
    {
        _categoryName = categoryName;
        _sLoggerConfiguration = sLoggerConfiguration;

        if (SLoggerProvider.IsFirst)
        {
            SLoggerProvider.IsFirst = false;

            // 创建文件路径
            DirectoryInfo dic = new DirectoryInfo(path);
            dic.Create();

            // 删除过期文件
            DeleteExceededLimitFiles(dic, DateTime.Now, false);
        }
    }

    /// <summary>
    /// 日志作用域，目前没有使用
    /// </summary>
    /// <typeparam name="TState"></typeparam>
    /// <param name="state"></param>
    /// <returns></returns>
    public IDisposable BeginScope<TState>(TState state) => null;

    /// <summary>
    /// 日志级别是否启用
    /// </summary>
    /// <param name="logLevel"></param>
    /// <returns></returns>
    public bool IsEnabled(LogLevel logLevel) => logLevel >= _sLoggerConfiguration.MinLogLevel;

    /// <summary>
    /// 添加日志时的框架自动调用的方法
    /// </summary>
    /// <typeparam name="TState"></typeparam>
    /// <param name="logLevel"></param>
    /// <param name="eventId"></param>
    /// <param name="state"></param>
    /// <param name="exception"></param>
    /// <param name="formatter"></param>
    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        try
        {
            // 没有启用的级别直接跳过
            if (IsEnabled(logLevel) == false) return;

            // 生成结构化日志体
            var lm = new LogMessage
            {
                EventId = eventId,
                Timestamp = DateTime.Now,
                LogLevel = logLevel,
                CategoryName = _categoryName,
                Message = formatter(state, exception),
                Exception = exception,
            };

            // 把所有不是枚举规定的类型的日志都转换为无标识日志
            if (SLoggerProvider.SLoggerSigns.Contains(lm.CategoryName) == false) lm.CategoryName = "";

            // 根据模板生成消息
            string msg = "";

            switch (logLevel)
            {
                case LogLevel.Warning:
                    msg = $"[{lm.Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{lm.LogLevel}]{(string.IsNullOrWhiteSpace(lm.CategoryName) ? "" : $" [{lm.CategoryName}]")}: \x1B[33m{lm.Message}{(lm.Exception == null ? "" : $"\n{lm.Exception}")}\x1B[0m";
                    msg = msg.EndsWith("\n") ? msg : $"{msg}\n";
                    break;
                case LogLevel.Error:
                case LogLevel.Critical:
                    msg = $"[{lm.Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{lm.LogLevel}]{(string.IsNullOrWhiteSpace(lm.CategoryName) ? "" : $" [{lm.CategoryName}]")}: \x1B[31m{lm.Message}{(lm.Exception == null ? "" : $"\n{lm.Exception}")}\x1B[0m";
                    msg = msg.EndsWith("\n") ? msg : $"{msg}\n";
                    break;
                default:
                    msg = $"[{lm.Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{lm.LogLevel}]{(string.IsNullOrWhiteSpace(lm.CategoryName) ? "" : $" [{lm.CategoryName}]")}: \x1B[32m{lm.Message}{(lm.Exception == null ? "" : $"\n{lm.Exception}")}\x1B[0m";
                    msg = msg.EndsWith("\n") ? msg : $"{msg}\n";
                    break;
            }

            Console.Write(msg);

            lock (_lock)
            {
                msg = $"[{lm.Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{lm.LogLevel}]{(string.IsNullOrWhiteSpace(lm.CategoryName) ? "" : $" [{lm.CategoryName}]")}: {lm.Message}{(lm.Exception == null ? "" : $"\n{lm.Exception}")}";
                msg = msg.EndsWith("\n") ? msg : $"{msg}\n";
                WriteToFile(lm, uTF8Encoding.GetBytes(msg), false);
            }

        }
        catch { }
    }

    private void WriteToFile(LogMessage lm, Span<byte> buffer, bool isCurrentRoll)
    {
        string key = $"{lm.LogLevel}{lm.CategoryName}{lm.Timestamp:yyyyMMdd}";
        if (!SLoggerProvider.SLoggerWriters.TryGetValue(key, out var writer) || writer == null)
        {
            string oldKey = $"{lm.LogLevel}{lm.CategoryName}{lm.Timestamp.AddDays(-1):yyyyMMdd}";
            if (SLoggerProvider.SLoggerWriters.TryGetValue(oldKey, out var oldWriter) && oldWriter != null)
            {
                oldWriter.FileStream.Dispose();
                SLoggerProvider.SLoggerWriters.Remove(oldKey);
            }
            writer = GetWriter(lm, isCurrentRoll);
        }
        var len = writer.RemainingWritableLength;
        if (len > buffer.Length)
        {
            writer.FileStream.Write(buffer);
            writer.FileStream.Flush();
            writer.RemainingWritableLength -= buffer.Length;
        }
        else
        {
            // 防止截断中文字符造成乱码
            for (; len >= 0; len--) if ((buffer[len] & 0xC0) != 0x80) break;
            writer.FileStream.Write(buffer[..len]);
            writer.FileStream.Flush();
            writer.Dispose();
            SLoggerProvider.SLoggerWriters.Remove(key);
            WriteToFile(lm, buffer[len..], true);
        }
    }

    private SLoggerWriter GetWriter(LogMessage lm, bool isCurrentRoll)
    {
        string namePrefix = $"{(string.IsNullOrWhiteSpace(lm.CategoryName) ? "" : $"{lm.CategoryName}_")}{lm.LogLevel}_";
        string name = $"{namePrefix}{lm.Timestamp:yyyyMMdd}";
        DirectoryInfo dic = new DirectoryInfo(path);
        dic.Create();
        int i = dic.GetFiles($"{name}*").Select(x => Convert.ToInt32(SLoggerProvider.LoggerIndexRegex.Match(x.Name).Value.CustomSubString(9, 10))).OrderByDescending(x => x).FirstOrDefault();
        var filePath = Path.Combine(path, $"{name}_{i}.log");
        var fileInfo = new FileInfo(filePath);
        int len = _sLoggerConfiguration.RetainedFileSizeLimit * 1024 * 1024;
        if (fileInfo.Exists)
        {
            fileInfo.Refresh();
            int fileLen = Convert.ToInt32(fileInfo.Length);
            if (fileLen >= len || isCurrentRoll)
            {
                filePath = Path.Combine(path, $"{name}_{i + 1}.log");
                DeleteExceededLimitFiles(dic, lm.Timestamp, true, namePrefix, true);
            }
            else
            {
                len = Convert.ToInt32(len - fileLen);
                DeleteExceededLimitFiles(dic, lm.Timestamp, true, namePrefix);
            }
        }
        else
        {
            DeleteExceededLimitFiles(dic, lm.Timestamp, true, namePrefix);
        }
        var writer = new SLoggerWriter
        {
            LogLevel = lm.LogLevel,
            CategoryName = lm.CategoryName,
            FilePath = filePath,
            RemainingWritableLength = len,
            FileStream = new FileStream(filePath, FileMode.Append, FileAccess.Write),
        };
        string key = $"{lm.LogLevel}{lm.CategoryName}{lm.Timestamp:yyyyMMdd}";
        SLoggerProvider.SLoggerWriters.Add(key, writer);
        return writer;
    }

    private void DeleteExceededLimitFiles(DirectoryInfo dic, DateTime dateTime, bool includeCountLimit = false, string namePrefix = "", bool isNew = false)
    {
        if (dateTime == DateTime.MinValue) dateTime = DateTime.Now;

        var files = dic.GetFiles();
        IEnumerable<FileInfo> deletes = files.Where(x => DateTime.ParseExact(SLoggerProvider.LoggerDateRegex.Match(x.Name).Value, "yyyyMMdd", null) < dateTime.AddDays(-_sLoggerConfiguration.RetainedFileDayLimit));
        if (deletes?.Count() > 0)
            foreach (var item in deletes)
                if (item.Exists)
                    item.Delete();

        if (includeCountLimit)
        {
            var retainedFileCountLimit = isNew ? _sLoggerConfiguration.RetainedFileCountLimit - 1 : _sLoggerConfiguration.RetainedFileCountLimit;
            deletes = files.Where(x => x.Name.StartsWith($"{namePrefix}")).OrderByDescending(x => DateTime.ParseExact(SLoggerProvider.LoggerDateRegex.Match(x.Name).Value, "yyyyMMdd", null)).ThenByDescending(x => Convert.ToInt32(SLoggerProvider.LoggerIndexRegex.Match(x.Name).Value.CustomSubString(9, 10)));
            if (retainedFileCountLimit > 0 && deletes?.Count() > retainedFileCountLimit)
            {
                deletes = deletes.Skip(retainedFileCountLimit);
                foreach (var item in deletes)
                    if (item.Exists)
                        item.Delete();
            }
        }
    }
}

/// <summary>
/// 
/// </summary>
public class SLoggerConfiguration
{
    /// <summary>
    /// 最小日志级别（appsettings 中优先级更高，这里默认配置最低，通过配置文件调整日志级别）
    /// </summary>
    public LogLevel MinLogLevel { get; set; } = LogLevel.Trace;
    /// <summary>
    /// 保留天数
    /// </summary>
    public int RetainedFileDayLimit { get; set; } = 30;
    /// <summary>
    /// 保留文件个数
    /// </summary>
    public int RetainedFileCountLimit { get; set; } = 30;
    /// <summary>
    /// 单个文件大小限制，单位为MB
    /// </summary>
    public int RetainedFileSizeLimit { get; set; } = 16;
}

/// <summary>
/// 
/// </summary>
public static class SLoggerExtensions
{
    /// <summary>
    /// 
    /// </summary>
    /// <param name="builder"></param>
    /// <param name="sLoggerConfiguration"></param>
    /// <returns></returns>
    public static ILoggingBuilder AddSLogger(this ILoggingBuilder builder, SLoggerConfiguration sLoggerConfiguration = null)
    {
        builder.AddProvider(new SLoggerProvider(sLoggerConfiguration ?? new SLoggerConfiguration()));
        return builder;
    }

    #region 记录日志扩展
    /// <summary>
    /// 
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="logLevel"></param>
    /// <param name="message"></param>
    /// <param name="args"></param>
    public static void Write(this ILogger logger, LogLevel logLevel, string message, params object[] args)
    {
        logger.Log(logLevel, 0, null, message, args);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="exception"></param>
    /// <param name="logLevel"></param>
    /// <param name="message"></param>
    /// <param name="args"></param>
    public static void Write(this ILogger logger, LogLevel logLevel, Exception exception, string message, params object[] args)
    {
        logger.Log(logLevel, 0, exception, message, args);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="message"></param>
    /// <param name="args"></param>
    public static void Trace(this ILogger logger, string message, params object[] args)
    {
        logger.Log(LogLevel.Trace, 0, null, message, args);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="exception"></param>
    /// <param name="message"></param>
    /// <param name="args"></param>
    public static void Trace(this ILogger logger, Exception exception, string message, params object[] args)
    {
        logger.Log(LogLevel.Trace, 0, exception, message, args);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="message"></param>
    /// <param name="args"></param>
    public static void Debug(this ILogger logger, string message, params object[] args)
    {
        logger.Log(LogLevel.Debug, 0, null, message, args);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="exception"></param>
    /// <param name="message"></param>
    /// <param name="args"></param>
    public static void Debug(this ILogger logger, Exception exception, string message, params object[] args)
    {
        logger.Log(LogLevel.Debug, 0, exception, message, args);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="message"></param>
    /// <param name="args"></param>
    public static void Information(this ILogger logger, string message, params object[] args)
    {
        logger.Log(LogLevel.Information, 0, null, message, args);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="exception"></param>
    /// <param name="message"></param>
    /// <param name="args"></param>
    public static void Information(this ILogger logger, Exception exception, string message, params object[] args)
    {
        logger.Log(LogLevel.Information, 0, exception, message, args);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="message"></param>
    /// <param name="args"></param>
    public static void Warning(this ILogger logger, string message, params object[] args)
    {
        logger.Log(LogLevel.Warning, 0, null, message, args);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="exception"></param>
    /// <param name="message"></param>
    /// <param name="args"></param>
    public static void Warning(this ILogger logger, Exception exception, string message, params object[] args)
    {
        logger.Log(LogLevel.Warning, 0, exception, message, args);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="message"></param>
    /// <param name="args"></param>
    public static void Error(this ILogger logger, string message, params object[] args)
    {
        logger.Log(LogLevel.Error, 0, null, message, args);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="exception"></param>
    /// <param name="message"></param>
    /// <param name="args"></param>
    public static void Error(this ILogger logger, Exception exception, string message, params object[] args)
    {
        logger.Log(LogLevel.Error, 0, exception, message, args);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="message"></param>
    /// <param name="args"></param>
    public static void Critical(this ILogger logger, string message, params object[] args)
    {
        logger.Log(LogLevel.Critical, 0, null, message, args);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="exception"></param>
    /// <param name="message"></param>
    /// <param name="args"></param>
    public static void Critical(this ILogger logger, Exception exception, string message, params object[] args)
    {
        logger.Log(LogLevel.Critical, 0, exception, message, args);
    }
    #endregion

    /// <summary>
    /// 
    /// </summary>
    /// <param name="str"></param>
    /// <param name="skip"></param>
    /// <param name="len"></param>
    /// <returns></returns>
    public static string CustomSubString(this string str, int skip, int len)
    {
        if (string.IsNullOrWhiteSpace(str)) return "0";
        var tmp = str.Substring(skip, str.Length - len);
        return tmp.Length == 0 ? "0" : tmp;
    }
}

/// <summary>
/// 
/// </summary>
public class SLoggerProvider : ILoggerProvider
{
    private readonly SLoggerConfiguration _sLoggerConfiguration;
    /// <summary>
    /// 日志标记（根据标记分文件）
    /// </summary>
    internal readonly static string[] SLoggerSigns = Enum.GetNames<SLoggerSign>();
    /// <summary>
    /// 
    /// </summary>
    internal readonly static Regex LoggerDateRegex = new Regex(@"(\d{8})", RegexOptions.Compiled);
    /// <summary>
    /// 
    /// </summary>
    internal readonly static Regex LoggerIndexRegex = new Regex(@"(\d{8})_(\d*)\.", RegexOptions.Compiled);
    /// <summary>
    /// 文件写入列表
    /// </summary>
    internal static Dictionary<string, SLoggerWriter> SLoggerWriters = new Dictionary<string, SLoggerWriter>();

    internal static bool IsFirst = true;

    /// <summary>
    /// 
    /// </summary>
    public SLoggerProvider(SLoggerConfiguration sLoggerConfiguration)
    {
        _sLoggerConfiguration = sLoggerConfiguration;
    }

    /// <summary>
    /// 创建日志对象
    /// </summary>
    /// <param name="categoryName"></param>
    /// <returns></returns>
    public ILogger CreateLogger(string categoryName = "")
    {
        return new SLogger(categoryName, _sLoggerConfiguration);
    }

    /// <summary>
    /// 
    /// </summary>
    public void Dispose()
    {
        foreach (var sLoggerWriter in SLoggerWriters)
        {
            sLoggerWriter.Value.FileStream.Dispose();
        }
        SLoggerWriters.Clear();
        SLoggerWriters = null;
    }
}

/// <summary>
/// 
/// </summary>
public enum SLoggerSign
{
    /// <summary>
    /// Sql 日志
    /// </summary>
    Sql,
}

/// <summary>
/// 
/// </summary>
public class SLoggerWriter : IDisposable
{
    /// <summary>
    /// 
    /// </summary>
    public LogLevel LogLevel { get; set; }
    /// <summary>
    /// 
    /// </summary>
    public string CategoryName { get; set; }
    /// <summary>
    /// 
    /// </summary>
    public string FilePath { get; set; }
    /// <summary>
    /// 剩余可写长度
    /// </summary>
    public int RemainingWritableLength { get; set; }
    /// <summary>
    /// 
    /// </summary>
    public FileStream FileStream { get; set; }
    /// <summary>
    /// 
    /// </summary>
    public void Dispose()
    {
        FileStream.Dispose();
    }
}

/// <summary>
/// 
/// </summary>
public class LogMessage
{

    /// <summary>
    /// 
    /// </summary>
    public EventId EventId { get; set; }

    /// <summary>
    /// 
    /// </summary>
    public DateTime Timestamp { get; set; }

    /// <summary>
    /// 
    /// </summary>
    public LogLevel LogLevel { get; set; }

    /// <summary>
    /// 
    /// </summary>
    public string CategoryName { get; set; }

    /// <summary>
    /// 
    /// </summary>
    public string Message { get; set; }

    /// <summary>
    /// 
    /// </summary>
    public Exception Exception { get; set; }
}
